package com.jzo2o.orders.manager.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.orders.dto.request.OrderCancelReqDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.constants.MqConstants;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.utils.*;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.manager.client.CustomerClient;
import com.jzo2o.orders.manager.client.FoundationsClient;
import com.jzo2o.orders.manager.job.OrderJob;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCanceledService;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.jzo2o.rabbitmq.client.RabbitClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.text.StrBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {


    @Autowired
    private CustomerClient customerClient;

    @Autowired
    private FoundationsClient foundationsClient;

    @Autowired
    private IOrdersCreateService owner;

    /**
     * 预约下单
     *
     * @param dto
     * @return
     */
    @Override
    public PlaceOrderResDTO place(PlaceOrderReqDTO dto) {

        //1.调用Feign接口,查询服务地址AddressBook信息
        AddressBookResDTO address = customerClient.findAddressDetailById(dto.getAddressBookId());
        if (address == null) {
            throw new BadRequestException("未选择服务地址");
        }
        //2.调用Feign接口,查询服务Serve信息
        ServeAggregationResDTO serve = foundationsClient.serveDetailById(dto.getServeId());
        if (serve == null || serve.getSaleStatus() != 2) {
            throw new BadRequestException("服务数据有问题");
        }
        //3.封装订单数据
        Orders orders = new Orders();

        //3.1 订单基础信息
        orders.setId(generateOrderId());//订单Id
        orders.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());//订单状态
        orders.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());//支付状态 2未支付
        orders.setDisplay(1);//是否展示 1展示
        orders.setServeStartTime(dto.getServeStartTime());//服务开始时间
        orders.setSortBy(DateUtils.toEpochMilli(dto.getServeStartTime()) + orders.getId() % 10000);//排序字段（serve_start_time秒级时间戳+订单id后6位）

        //3.3 服务信息
        orders.setServeId(serve.getId());//服务Id
        orders.setPrice(serve.getPrice());//单价
        orders.setServeTypeId(serve.getServeTypeId());//服务类型Id
        orders.setServeTypeName(serve.getServeTypeName());//服务类型名称
        orders.setServeItemId(serve.getServeItemId());//服务项Id
        orders.setServeItemName(serve.getServeItemName());//服务项名称
        orders.setServeItemImg(serve.getServeItemImg());//服务项图片
        orders.setUnit(serve.getUnit());//服务单位
        orders.setCityCode(serve.getCityCode());//城市编码

        //3.4 下单人信息
        orders.setUserId(UserContext.currentUserId());//下单人Id
        orders.setServeAddress(new StrBuilder()
                .append(address.getProvince()).append(address.getCity()).append(address.getCounty()).append(address.getAddress()).toString()); //下单详细地址
        orders.setContactsPhone(address.getPhone());//联系人电话
        orders.setContactsName(address.getName());//联系人姓名
        orders.setLat(address.getLat());//纬度
        orders.setLon(address.getLon());//经度

        //3.5 订单金额信息
        orders.setPurNum(dto.getPurNum());//购买数量
        orders.setDiscountAmount(BigDecimal.ZERO);//优惠金额 暂时写零
        orders.setTotalAmount(serve.getPrice().multiply(BigDecimal.valueOf(dto.getPurNum()))); //订单总金额
        orders.setRealPayAmount(orders.getTotalAmount().subtract(orders.getDiscountAmount())); //实际支付金额

        //3.6 支付信息（支付完再处理）

        //3.7 保存数据
        owner.addOrder(orders);

        //3.8 下单后就要立即发送延迟消息 (15分钟后如果没有支付就要取消订单)
        rabbitClient.sendMsg(MqConstants.Exchanges.ORDERS, MqConstants.RoutingKeys.ORDERS_CANCEL_KEY, orders.getId().toString(),
                20000, IdUtils.getSnowflakeNextId(), false);
        //4.返回响应结果,订单Id
        return new PlaceOrderResDTO(orders.getId());
    }

    @Autowired
    private RabbitClient rabbitClient;

    @Transactional
    public void addOrder(Orders orders) {
        boolean result = save(orders);
        //int i = 1 / 0;
        if (!result) {
            throw new DBException("下单失败");
        }
    }


    @Autowired
    private RedisTemplate redisTemplate;


    private static final String ORDER_SERIAL_NUM = "order:serial:num";


    /**
     * 订单主键Id生成策略 19位：2位年+2位月+2位日+13位序号
     */
    public Long generateOrderId() {
        //1.前置部分是年月日
        Long idPrefix = DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd") * 10000000000000L;

        //2.后置部分是Redis自增策略
        Long serialNum = redisTemplate.opsForValue().increment(ORDER_SERIAL_NUM);

        return idPrefix + serialNum;
    }

    @Autowired
    private NativePayApi nativePayApi;

    @Autowired
    private TradeProperties tradeProperties;

    /**
     * 订单支付
     *
     * @param id
     * @param dto
     * @return
     */
    @Override
    public OrdersPayResDTO pay(Long id, OrdersPayReqDTO dto) {
        //1.查询订单信息,看订单是否存在
        Orders orders = this.getById(id);
        if (ObjectUtils.isNull(orders)) {
            throw new BadRequestException("订单号不存在");
        }
        //2.判断支付状态是否是待支付 只有待支付才能去支付
        if (ObjectUtils.notEqual(orders.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())) {
            throw new BadRequestException("订单状态异常(非法订单)");
        }
        //3.准备支付微服务的Feign接口要传入的参数
        NativePayReqDTO nativePayDto = new NativePayReqDTO();

        nativePayDto.setProductAppId(tradeProperties.getProductAppId()); //业务系统标识
        nativePayDto.setProductOrderNo(id); // 业务系统订单号
        nativePayDto.setTradingChannel(dto.getTradingChannel()); // 支付渠道
//        nativePayDto.setTradingAmount(orders.getRealPayAmount()); // 交易金额，单位：元
        nativePayDto.setTradingAmount(BigDecimal.valueOf(0.01)); // 便于测试
        nativePayDto.setMemo(orders.getServeItemName()); // 商品描述(服务项名称)

        nativePayDto.setChangeChannel(false); // 是否切换支付渠道 true切换支付渠道 false不切换支付渠道
        if (StringUtils.isNotBlank(orders.getTradingChannel()) && ObjectUtils.notEqual(dto.getTradingChannel(), orders.getTradingChannel())) {
            // 切换支付渠道
            nativePayDto.setChangeChannel(true);
        }

        //根据支付渠道判断到底是选择微信还是支付宝 的商户号
        if (ObjectUtils.equals(dto.getTradingChannel(), PayChannelEnum.WECHAT_PAY)) {
            // 微信支付
            nativePayDto.setEnterpriseId(tradeProperties.getWechatEnterpriseId());// 商户号

            //4.调用支付微服务Feign接口
            NativePayResDTO nativePayResDTO = nativePayApi.createDownLineTrading(nativePayDto);
            if (ObjectUtils.isNull(nativePayResDTO)) {
                throw new BadRequestException("调用支付微服务Feign接口失败");
            }
            //5.更新订单表 字段的值

            owner.updateOrders(id, nativePayResDTO.getTradingOrderNo(), nativePayResDTO.getTradingChannel());

            //6.封装响应结果并返回
            OrdersPayResDTO ordersPayResDTO = BeanUtils.copyBean(nativePayResDTO, OrdersPayResDTO.class);
            ordersPayResDTO.setPayStatus(orders.getPayStatus());
            //设置支付状态
            return ordersPayResDTO;
        } else {
            // 支付宝支付
            nativePayDto.setEnterpriseId(tradeProperties.getAliEnterpriseId());// 商户号
            // TODO 支付宝支付待开发 可能需要自己研究了
            throw new BadRequestException("支付宝支付待开发");
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrders(Long id, Long tradingOrderNo, String tradingChannel) {
        boolean flag = lambdaUpdate().set(Orders::getTradingOrderNo, tradingOrderNo) // 支付服务交易单号
                .set(Orders::getTradingChannel, tradingChannel) // 支付渠道
                .eq(Orders::getId, id)
                .update();
        if (!flag) {
            throw new DBException("更新订单表失败");
        }
    }

    @Resource
    private TradingApi tradingApi;

    /**
     * 查询订单支付结果
     *
     * @param id
     * @return
     */
    @Override
    public OrdersPayResDTO payResult(Long id) {
        //1.查询订单信息,看订单是否存在
        Orders orders = this.getById(id);
        if (ObjectUtils.isNull(orders)) {
            throw new BadRequestException("订单号不存在");
        }
        //2.判断订单状态是否是待支付 不是待支付的话直接返回结果
        OrdersPayResDTO ordersPayResDTO = BeanUtils.copyBean(orders, OrdersPayResDTO.class);
        if (ObjectUtils.notEqual(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
            return ordersPayResDTO;
        }
        //3.判断是否有交易单号
        if (ObjectUtils.isNull(orders.getTradingOrderNo())) {
            throw new BadRequestException("非法订单号");
        }
        //4.根据交易单号调用查询支付订单的Feign接口
        TradingResDTO tradingOrderNo = tradingApi.findTradResultByTradingOrderNo(orders.getTradingOrderNo());
        if (ObjectUtils.isNull(tradingOrderNo)) {
            throw new BadRequestException("查询支付订单失败");
        }
        //5.根据查询结果 支付成功: 更新订单表字段(pay_time transaction_id pay_status orders_status)
        if (ObjectUtils.equals(tradingOrderNo.getTradingState().getCode(), TradingStateEnum.YJS.getCode())) {
            // 这是支付成功的情况 更新订单表字段
            owner.paySuccess(id, tradingOrderNo.getPaySuccessTime(), tradingOrderNo.getTransactionId());

            ordersPayResDTO = BeanUtils.copyBean(tradingOrderNo, OrdersPayResDTO.class);
            ordersPayResDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());

            return ordersPayResDTO;

        }
        //6.没有支付成功先不管他
        return null;
    }

    /**
     * 支付成功更新订单
     *
     * @param id
     * @param payTime
     * @param transactionId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paySuccess(Long id, LocalDateTime payTime, String transactionId) {
        // 这是支付成功的情况 更新订单表字段
        boolean flag = lambdaUpdate().set(Orders::getPayTime, payTime)
                .set(Orders::getTransactionId, transactionId)
                .set(Orders::getPayStatus, TradingStateEnum.YJS.getCode())
                .set(Orders::getOrdersStatus, OrderStatusEnum.DISPATCHING.getStatus())
                .eq(Orders::getId, id)
                .update();
        if (!flag) {
            throw new DBException("更新订单表失败");
        }
    }

    @Autowired
    private IOrdersCanceledService ordersCanceledService;

    /**
     * 取消订单 (待支付和派送中)
     *
     * @param orderCancelReqDTO
     */
    @Override
    public void cancel(OrderCancelReqDTO orderCancelReqDTO) {
        //1.查询订单信息,看订单是否存在
        Orders orders = this.getById(orderCancelReqDTO.getId());
        if (orders == null) {
            throw new BadRequestException("订单号不存在");
        }
        //2.判断订单状态是否是待支付 或 派送中 不是待支付 或 派送中 的话直接返回结果
        //将待支付和派单中的状态放到一个集合中
        Integer noPayStatus = OrderStatusEnum.NO_PAY.getStatus();// 待支付状态
        Integer dispatchingStatus = OrderStatusEnum.DISPATCHING.getStatus();// 派送中状态
        List<Integer> orderStatusList = Arrays.asList(noPayStatus, dispatchingStatus);
        if (!orderStatusList.contains(orders.getOrdersStatus())) {
            throw new BadRequestException("订单状态不允许取消(非法订单)");
        }
        //3.判断订单状态是待支付(不用进行退单操作)
        if (ObjectUtils.equals(orders.getOrdersStatus(), noPayStatus)) {
            // 待支付状态 更新订单表状态 和 订单取消表
            owner.cancelNoPayUpdate(orderCancelReqDTO.getId(), orderCancelReqDTO.getCancelReason(), false);
        } else {
            // 派送中状态
            owner.cancelOrderByDispatching(orderCancelReqDTO, orders);
        }
    }

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrderByDispatching(OrderCancelReqDTO orderCancelReqDTO, Orders orders) {
        //1.需要修改订单状态为已关闭
        boolean flag = lambdaUpdate().set(Orders::getOrdersStatus, OrderStatusEnum.CLOSED.getStatus()) // 已关闭
                .set(Orders::getRefundStatus, OrderRefundStatusEnum.REFUNDING.getStatus()) // 退款中
                .eq(Orders::getId, orderCancelReqDTO.getId())
                .update();
        if (!flag) {
            throw new DBException("订单表更新失败");
        }
        //2.向订单取消表中添加一条数据
        OrdersCanceled orderCancel = new OrdersCanceled();
        orderCancel.setCancellerId(UserContext.currentUserId()); // 取消人id
        orderCancel.setCancelerName(UserContext.currentUser().getName()); // 取消人名称
        orderCancel.setCancellerType(UserContext.currentUser().getUserType()); // 取消人类型
        orderCancel.setCancelReason(orderCancelReqDTO.getCancelReason()); // 取消原因
        orderCancel.setCancelTime(LocalDateTime.now()); // 取消时间
        boolean flag1 = ordersCanceledService.save(orderCancel);
        if (!flag1) {
            throw new DBException("保存订单取消表失败");
        }
        //3.向订单退款表orders_canceled中添加一条记录
        OrdersRefund orderRefund = new OrdersRefund();
        orderRefund.setId(orderCancelReqDTO.getId());// 订单id
        orderRefund.setTradingOrderNo(orders.getTradingOrderNo()); // 支付服务交易单号
//            orderRefund.setRealPayAmount(orders.getRealPayAmount()); // 实际支付金额
        orderRefund.setRealPayAmount(BigDecimal.valueOf(0.01));
        boolean flag2 = ordersRefundService.save(orderRefund);
        if (!flag2) {
            throw new DBException("订单退款表添加失败");
        }

        //4. 执行退款操作 (自定义线程池执行提速) 复用定时任务处理单个订单退款的业务
        OrderJob orderJob = applicationContext.getBean(OrderJob.class);
        orderJob.ordersRefundHandle(orderRefund);
    }

    @Autowired
    private IOrdersRefundService ordersRefundService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelNoPayUpdate(Long id, String cancelReason, boolean isSystem) {
        // 更新订单表状态
        boolean flag = this.lambdaUpdate().set(Orders::getOrdersStatus, OrderStatusEnum.CANCELED.getStatus())
                .eq(Orders::getId, id)
                .update();
        if (!flag) {
            throw new DBException("更新订单表失败");
        }
        // 保存订单取消表
        OrdersCanceled orderCancel = new OrdersCanceled();
        if (!isSystem) {
            orderCancel.setCancellerId(UserContext.currentUserId());
            orderCancel.setCancelerName(UserContext.currentUser().getName());
            orderCancel.setCancellerType(UserContext.currentUser().getUserType());
        }
        orderCancel.setCancelReason(cancelReason);
        orderCancel.setCancelTime(LocalDateTime.now());
        boolean flag1 = ordersCanceledService.save(orderCancel);
        if (!flag1) {
            throw new DBException("保存订单取消表失败");
        }
    }


}